package edu.washington.cs.workflow.analysis.guistructure;

import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.uispec4j.Panel;
import org.uispec4j.Trigger;
import org.uispec4j.UIComponent;
import org.uispec4j.UISpec4J;
import org.uispec4j.Window;
import org.uispec4j.interception.WindowHandler;
import org.uispec4j.interception.WindowInterceptor;

import edu.washington.cs.workflow.analysis.guistructure.uievents.ButtonEvent;
import edu.washington.cs.workflow.analysis.guistructure.uievents.TextComponentEvent;
import edu.washington.cs.workflow.analysis.guistructure.uievents.UIEvent;
import edu.washington.cs.workflow.analysis.guistructure.uievents.UIEvent.WINDOW;
import edu.washington.cs.workflow.analysis.guistructure.uievents.UIEventFactory;
import edu.washington.cs.workflow.analysis.guistructure.uievents.UIEventOutcome;
import edu.washington.cs.workflow.analysis.guistructure.uievents.UIEventThreadExecutor;
import edu.washington.cs.workflow.util.Utils;

/**
 * Randomly explore the available UI actions
 * */
public class UIActionExecutor {

	static {
		UISpec4J.init();
	}
	
	private Collection<EventSequence> pool = new LinkedList<EventSequence>();
	private Collection<EventSequence> outputSeqs = new LinkedList<EventSequence>();
	private Collection<EventSequence> errorSeqs = new LinkedList<EventSequence>();
	
	public final String mainClassName;
	public final Class<?> mainClass;
	public final String[] args;
	
	public static int windowsPopTimeLimit = 1000;
	
	/**
	 * some internal data structure for keeping actions and methods
	 * */
	final Set<String> covered_actions = new LinkedHashSet<String>();
	final Set<String> covered_methods = new LinkedHashSet<String>();
	
	public UIActionExecutor(String mainClassName, String[] args) {
		Utils.checkNotNull(mainClassName);
		Utils.checkNotNull(args);
		this.mainClassName = mainClassName;
		this.args = args;
		try {
		    mainClass = Class.forName(mainClassName);
		} catch (Throwable e) {
			throw new Error(e);
		}
		//set the time limit
		UISpec4J.setWindowInterceptionTimeLimit(windowsPopTimeLimit);
	}
	
	public Panel getFreshMainWindow() {
		return MainWindowCreator.getFreshMainWindow(this.mainClass, this.args);
	}
	
	/**
	 * 1. New actions after performing certain actions on the old dialog. check after the finish
	 *    of an action
	 * 2. Use some recursive calls
	 * */
	boolean usethread = false;
	
	boolean ignoremodal = false;
	
	boolean ignorenonmodal = false;
	
	public void setUsethread(boolean use) {
		this.usethread = use;
	}
	
	public void setIgnoreModal(boolean ignore) {
		this.ignoremodal = ignore;
	}
	
	public void setIgnoreNonmodal(boolean ignore) {
		this.ignorenonmodal = ignore;
	}
	
	public Collection<EventSequence> executeUIActions() {
		Collection<EventSequence> seqs = new LinkedList<EventSequence>();
		
		//the event on the main window
		Panel mainWindow = this.getFreshMainWindow();
		//the orders returned here is already random
		Collection<UIEvent> events  = Spec4JUtils.allAvailableUIEvents(mainWindow);
		
		//the queue to store next ui event
		final List<UIEvent> queue = new LinkedList<UIEvent>();
		queue.addAll(events);
		
		//the set to keep all the generated ui events
		final Set<String> allEventsGen = new LinkedHashSet<String>();
		for(UIEvent event : events) {
//			allEventsGen.add(event.toString());
			allEventsGen.add(event.getDescription());
		}
		
		//do breath first search
		Utils.debugPrintln("The initial size of the UI event queue: " + queue.size());
		while(!queue.isEmpty()) {
			Utils.debugPrintln("size of queue: " + queue.size());
			final UIEvent event = queue.remove(0);
			//skip if the action can not be fired
			if(!event.canFireEvent()) {
				Utils.debugPrintln("Can not fire? " + event.canFireEvent() + "  of event: " + event.getDescription());
				continue;
			}
			//Skip the quit action here
			if(Spec4JUtils.isQuitEvent(event)) {
				continue;
			}
			//skip the already covered action
//			String actionStr = event.toString();
			final String actionStr = event.getDescription();
			if(this.covered_actions.contains(actionStr)) {
				Utils.debugPrintln("skip already-existed action: " + actionStr);
				continue;
			}
			
			if(EvaluationHelper.isSkippedGUIElement(actionStr)) {
				Utils.debugPrintln("skip actions that UISpec4J cannot handle: " + actionStr);
				continue;
			}
			
			WINDOW w = null;
			UIEventOutcome outcome = null;
			if(usethread) {
				outcome = UIEventThreadExecutor.executeUIEvent(event);
				if(outcome != null) {
					w = outcome.getWindow();
				}
			} else {
			    //fire the event
			    w = event.probeWindowInfo();
			    outcome = event.fireEvent();
			}
			if(outcome == null) {
				Utils.debugPrintln("No outcome for: " + event);
				this.covered_actions.add(actionStr); //still need to add to the set
				continue;
			}
			
			Utils.debugPrintln("has fired action: " + event + ", window: " + w + ",  window: " + outcome.isModalWindow());
			this.covered_actions.add(actionStr);
			
			//the cases of modal dialog and non-modal dialog
			if (w.equals(WINDOW.MODAL)) {
			  if(this.ignoremodal) {
				  continue;
			  }
			  Trigger t = outcome.getTrigger();
			  //FIXME ugly workaround for jedit
			  if(EvaluationHelper.isSkippedGUIElement(event.toString())) {
				  continue;
			  }
//			  if(event.toString().indexOf("Multifile Search Settings") != -1) {
//				  continue;
//			  }
			  try {
			      WindowInterceptor.init(t).process(new WindowHandler() {
			          public Trigger process(Window dialog) {
				          Utils.debugPrintln("popup a dialog: " + dialog.getDescription());
					      Collection<UIEvent> newAvailabeEvents = Spec4JUtils.allAvailableUIEvents(dialog);
//					      queue.addAll(newAvailabeEvents);
					      
					      Utils.debugPrintln("number of events: " + newAvailabeEvents.size()
					    		  + ", after performing: " + actionStr);
					      for(UIEvent modalEvent : newAvailabeEvents) {
					    	  Utils.debugPrintln("   modal event: " + modalEvent);
					    	  modalEvent.setAboveEvent(event); //record the last event
					      }
					
					      for(UIEvent e : newAvailabeEvents) {
//						      String new_action_str = e.toString();
						      String new_action_str = e.getDescription();
						      Utils.debugPrintln("  new action in modal dialog: " + new_action_str);
						      if(covered_actions.contains(new_action_str)) {
							      Utils.debugPrintln("skip already-existed action in the modal dialog: " + new_action_str);
							      continue;
						      }
						      if(EvaluationHelper.isSkippedGUIElement(e.toString())) {
						    	  Utils.debugPrintln("skip the event the tool cannot handle: " + e);
						    	  continue;
						      }
						      if((e.toString().indexOf("OK") != -1 || e.toString().indexOf("Cancel") != -1)
						    		  && EvaluationHelper.isSkippedGUIElement(e.toString())) {
						    	  Utils.debugPrintln("skip the event the tool cannot handle: " + e);
						    	  continue;
						      }
						      if(e.canFireEvent()) {
						    	  Utils.debugPrintln("going to fire action in modal dialog: " + e);
						    	  if(usethread) {
						    		  UIEventOutcome o = UIEventThreadExecutor.executeUIEvent(e);
						    		  if(o == null) {
						    			  Utils.debugPrintln("No outcome for executing: " + e);
						    		  }
						    	  } else {
							          e.probeWindowInfo();
							          e.fireEvent();
						    	  }
						      }
					      }
					      
					      //just frutely exit the modal dialog if all actions are executed
					      throw new Error();
					   }
				   }).run();
			   } catch (Throwable e) {
					e.printStackTrace();
			   }
			} else if (w.equals(WINDOW.NONMODAL)) {
				if(this.ignorenonmodal) {
					continue;
				}
				try {
					Trigger t = outcome.getTrigger();
					
					Window newDialog = WindowInterceptor.run(t);
					
					Collection<UIEvent> newAvailabeEvents = Spec4JUtils.allAvailableUIEvents(newDialog);
					Utils.debugPrintln("The number of events from the non-modal dialog: " + newAvailabeEvents.size());
					
//					queue.addAll(newAvailabeEvents);
					for(UIEvent e : newAvailabeEvents) {
						e.setAboveEvent(event); //track the above event
//						Utils.debugPrintln("  the new event from non-modal: " + e);
//						allEventsGen.add(e.toString());
						allEventsGen.add(e.getDescription());
					}
					
					//check to add to queue or not
					for(UIEvent e : newAvailabeEvents) {
						if(this.covered_actions.contains(e.getDescription())) {
							continue;
						} else {
							Utils.debugPrintln("add event: " + e.getDescription() + " to queue from non-modal");
							queue.add(e);
						}
					}
				} catch(Throwable e) {
					e.printStackTrace();
				}
			}
			
			//update the queue
			Collection<UIEvent> updatedEvents  = Spec4JUtils.allAvailableUIEvents(mainWindow);
			for(UIEvent updateEvent : updatedEvents) {
				String eventStr = updateEvent.getDescription();
				if(allEventsGen.contains(eventStr)) {
					continue;
				} else {
					Utils.debugPrintln("add event: " + eventStr + " from updated event.");
					queue.add(updateEvent);
					allEventsGen.add(eventStr);
				}
			}
		}
		
		return seqs;
	}
}